So this is a super brief intro into some of the cool things you can do with leaflet. There are comprehensive tutorials available online, for example here.
You will need to have installed the following packages to follow along:
install.packages("leaflet") #for mapping
install.packages("RColorBrewer") #for getting nice colours for your maps
install.packages("rgdal") #for getting data from TfGM API
install.packages("httr") #for getting data from TfGM API
install.packages("jsonlite") #for getting data from TfGM API
To make a map, just load the leaflet library:
library(leaflet)
You then create a map with this simple bit of code:
m <- leaflet() %>%
addTiles()
And just print it:
m
You might of course want to add some content to your map.
You can add a point manually:
m <- leaflet() %>%
addTiles() %>%
addMarkers(lng=-2.230899, lat=53.464987, popup="You are here")
m
Or many points manually:
latitudes = c(53.464987, 53.472726, 53.466649)
longitudes = c(-2.230899, -2.245481, -2.243421)
popups = c("You are here", "Here is another point", "Here is another point")
df = data.frame(latitudes, longitudes, popups)
m <- leaflet(data = df) %>%
addTiles() %>%
addMarkers(lng=~longitudes, lat=~latitudes, popup=~popups)
m
You can change the background as well. You can find a list of different basemaps here.
m <- leaflet(data = df) %>%
addProviderTiles("Stamen.Toner") %>%
addMarkers(lng=~longitudes, lat=~latitudes, popup=~popups)
m
You will most likely want to add data to your map form external sources, rather than manually creating points.
For example, I illustrate here with data from Manchester Open Data about public toilets:
publicToilets <- read.csv("http://www.manchester.gov.uk/open/download/downloads/id/171/public_toilets.csv")
Often spatial data will not come with latitude/longitude format, but with easting and northing. Leaflet (as far as I know) prefers lat/long so we might have to convert from BNG to WGS84.
First thing we might notice is that the coordinates are in Easting and Northing format, rather than Latitude/ Longitude:
publicToilets[,8:9]
## GeoX GeoY
## 1 383958 398732
## 2 384245 398416
## 3 383873 398647
## 4 383643 398363
## 5 383950 398449
## 6 383194 397863
## 7 383312 398333
## 8 383923 398633
## 9 383864 398033
There is a comprehensive step-by-step tutorial on converting coordinates here. I’ll just briefly demo this here.
#the library I'm using here is rgdal
library(rgdal)
## Loading required package: sp
## rgdal: version: 1.1-10, (SVN revision 622)
## Geospatial Data Abstraction Library extensions to R successfully loaded
## Loaded GDAL runtime: GDAL 1.11.4, released 2016/01/25
## Path to GDAL shared files: /Library/Frameworks/R.framework/Versions/3.3/Resources/library/rgdal/gdal
## Loaded PROJ.4 runtime: Rel. 4.9.1, 04 March 2015, [PJ_VERSION: 491]
## Path to PROJ.4 shared files: /Library/Frameworks/R.framework/Versions/3.3/Resources/library/rgdal/proj
## Linking to sp version: 1.2-3
#these are the variables for the coordinate system types
bng = "+init=epsg:27700"
latlong = "+init=epsg:4326"
#create coords
coords <- cbind(Easting = as.numeric(as.character(publicToilets$GeoX)),
Northing = as.numeric(as.character(publicToilets$GeoY)))
# create a SpatialPointsDataFrame
publicToiletsSPDF <- SpatialPointsDataFrame(coords, data = publicToilets, proj4string = CRS(bng))
#reproject with spTransform
publicToiletsSPDF_latlng <- spTransform(publicToiletsSPDF, CRS(latlong))
#extract coords into a column
publicToiletsSPDF_latlng@data$lng <- publicToiletsSPDF_latlng@coords[,1]
publicToiletsSPDF_latlng@data$lat <- publicToiletsSPDF_latlng@coords[,2]
Now you should have a reprojected spatial points data frame with latitude and longitude, ready to be mapped:
m <- leaflet(data = publicToiletsSPDF_latlng@data) %>%
addProviderTiles("Stamen.Toner") %>%
addMarkers(lng=~lng, lat=~lat, popup=~LocationText)
m
You can also make your markers tell you something about your data.
Let’s look at a different dataset.
This one from TfGM (accessed through their API). To use their API, you will have to get your own key. You can then use the code below, by replacing “enter your key here” with your key. To get your own key you simply have to register with TFGM on their site for developers.
api_key <- "enter your key here"
Anyway once you have a key, you can get some data about various different transporty things. For example, here we get some information about car parks:
library(httr) #library I use for getting data from API
library(jsonlite) #library I use for parsing the data into a dataframe
#get the data
req <- GET("https://api.tfgm.com/odata/Carparks?$expand=Location&$top=500",
add_headers("Ocp-Apim-Subscription-Key" = api_key))
stop_for_status(req)
#parse the data
thing <- content(req, as='text')
## No encoding supplied: defaulting to UTF-8.
thing2 <- fromJSON(thing)
#finally get coordinates to columns from the WKT
thing2$value$lon <- as.numeric(paste0("-",gsub(".*?([0-9]+[.][0-9]+).*", "\\1", thing2$value$Location$LocationSpatial$Geography$WellKnownText)))
thing2$value$lat <- as.numeric(gsub(".* ([-]*[0-9]+[.][0-9]+).*", "\\1", thing2$value$Location$LocationSpatial$Geography$WellKnownText))
Now we have some live data about car parks! Let’s map this!
We can set the size of the dots to indicate how many free spaces are currently available. We can also colour by a factor, let’s say by the ‘state’ of the carpark.
library(RColorBrewer) #library for getting nice colours for your maps
#set colour scheme:
pal <- colorFactor("Paired", thing2$value$State)
#can build a more complex popup by ysing paste:
popupText <- paste0("Name:",
thing2$value$Description,
"<br>",
"State: ",
thing2$value$State,
"<br>",
"Capacity: ",
thing2$value$Capacity)
leaflet(thing2$value) %>%
addProviderTiles("Stamen.Toner") %>%
addCircleMarkers(
lng = ~lon,
lat = ~lat,
radius = ~Capacity/75, #am making the numbers smaller otherwise we get giant blobs
fillColor = ~pal(State),
popup = popupText,
stroke = 0.01,
color = "black",
fillOpacity = 1,
weight=1
)
## Warning in doColorRamp(colorMatrix, x, alpha, ifelse(is.na(na.color), "", :
## '.Random.seed' is not an integer vector but of type 'NULL', so ignored
Might also want to add a legend. Can do this with the addLegend() function.
leaflet(thing2$value) %>%
addProviderTiles("Stamen.Toner") %>%
addCircleMarkers(
lng = ~lon,
lat = ~lat,
radius = ~Capacity/75, #am making the numbers smaller otherwise we get giant blobs
fillColor = ~pal(State),
popup = popupText,
stroke = 0.01,
color = "black",
fillOpacity = 1,
weight=1
) %>%
addLegend("bottomright", pal = pal, values = ~State,
title = "State of carpark",
opacity = 1)
leaflet(thing2$value) %>%
addProviderTiles("Stamen.Toner") %>%
addMarkers(
lng = ~lon,
lat = ~lat,
popup = ~Description,
clusterOptions = markerClusterOptions()
)
Can also easily produce thematic maps with leaflet, or play around with polygons.
You can import a shapefile you might already have on your computer. For example, I have this
fixMyStreet <- readOGR(dsn = "/Users/reka/Desktop/Data", "allDataAtLsoa")
## OGR data source with driver: ESRI Shapefile
## Source: "/Users/reka/Desktop/Data", layer: "allDataAtLsoa"
## with 4835 features
## It has 5 fields
fixMyStreet <- spTransform(fixMyStreet, CRS("+proj=longlat +datum=WGS84"))
#make a fancy popup
boroughs_popup <- paste0("<strong>LSOA: </strong>",
fixMyStreet@data$LSOA11NM,
"<br><strong>Number of incivility reports: </strong>",
fixMyStreet@data$n_inciv)
#create colour palette
# rcolourbrewer gives loads to choose from, here are some examples:
#DivergingBrBG, PiYG, PRGn, PuOr, RdBu, RdGy, RdYlBu, RdYlGn, Spectral
#QualitativeAccent, Dark2, Paired, Pastel1, Pastel2, Set1, Set2, Set3
#SequentialBlues, BuGn, BuPu, GnBu, Greens, Greys, Oranges, OrRd, PuBu, PuBuGn, PuRd, Purples, RdPu, Reds, YlGn, YlGnBu, YlOrBr, YlOrRd
pal <- colorNumeric(
palette = "YlOrRd",
domain = fixMyStreet@data$n_inciv
)
#make map
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
addPolygons( data = fixMyStreet,
stroke = TRUE, weight= 1, fillOpacity = 0.8,
color = ~pal(n_inciv),
popup = boroughs_popup
) %>%
#and add a Legend as well
addLegend(pal = pal,
values = fixMyStreet@data$n_inciv,
title = "Number of incivility reports",
labFormat = labelFormat(suffix = " incivs"),
opacity = 0.8
)
Can also map polygons from geojson files from the web. For example, here is a map of population density (number of persons per hectare) at the Output Area Level in Manchester:
geoData <- readLines("https://data.cdrc.ac.uk/dataset/35c1fb9d-df77-4261-9861-14fc75d6f26a/resource/da9e9f72-4d11-46e9-b33a-7a5e24b83d0c/download/cdrc-2013-mid-year-total-population-estimates-geodata-pack-lsoa-manchester-e08000003.geojson", warn = FALSE) %>%
paste(collapse = "\n") %>%
fromJSON(simplifyVector = FALSE)
value <- sapply(geoData$features, function(feat) {
feat$properties$value
})
pal <- colorQuantile("Greens", value)
# Add a properties$style list to each feature
geoData$features <- lapply(geoData$features, function(feat) {
feat$properties$style <- list(
fillColor = pal(
feat$properties$value)
#)
)
feat
})
# Add the now-styled GeoJSON object to the map
leaflet() %>% setView(lng = -2.230899, lat=53.464987, zoom = 10) %>%
addTiles() %>%
addGeoJSON(geoData, weight=1, fillOpacity = 0.8)
It is also possible to make the leaflet maps interactive, by integrating them with Shiny applications. The final dataset for this to experiment with comes from AccStats data for 2015, which can be accessed using the Transport for London API:
l = readLines(queryString, encoding="UTF-8", warn=FALSE)
d = fromJSON(l)
accidents <- data.frame(lapply(as.data.frame(d), as.character), stringsAsFactors=FALSE)
#also make sure data is in date format
accidents$date2 <- as.Date(accidents$date, "%Y-%m-%d")
NOTE: if you’re going to be using this code, you will have to make a developer account with TfL to generate your own app_id and app_key for a query string. OK now with this data we can create an app for looking at the number of accidents with different severity, and adjust the date range (within the year 2015) as we like.
First load shiny package.
library(shiny)
##
## Attaching package: 'shiny'
## The following object is masked from 'package:jsonlite':
##
## validate
And then create an app, with the leaflet map inside:
ui <- fluidPage(
titlePanel("Accidents in 2015"),
sidebarLayout(
sidebarPanel(
#date selector goes here
dateRangeInput("Date range", inputId = "date_range",
start = "2015-01-01",
end = "2015-12-31",
format = "yyyy-mm-dd"),
uiOutput('severitySelector',selected = "Fatal")
),
mainPanel(
#leaflet output goes here
leafletOutput("map", height = 800)
)
)
)
server <- function(input, output) {
severityChoices <- sort(unique(as.character(accidents$severity)))
#create the drop down menu with name country selector to put in placeholder in UI
output$severitySelector <- renderUI({
selectInput("severitySelect", label = "Select severity",
choices = as.list(severityChoices), selected = "Fatal")
})
#filter data based on dates
dateFiltered <- reactive({
thing <- accidents %>% filter(date2 %in% seq(input$date_range[1], input$date_range[2], by = "day") & severity %in% input$severitySelect)
})
#reactive map
output$map <- renderLeaflet({
leaflet(accidents) %>%
addProviderTiles("CartoDB.Positron") %>%
fitBounds(~min(lon), ~min(lat), ~max(lon), ~max(lat)) %>%
addLegend(position = "bottomleft", colors = c("#b10026", "#fd8d3c", "#ffeda0"),
labels = c("Fatal", "Serious", "Slight"), opacity = 1, title = "Severity")
})
observe({
pal <- colorFactor(c("#b10026", "#fd8d3c", "#ffeda0"), domain = c("Fatal", "Serious", "Slight"), ordered = TRUE)
leafletProxy("map", data = dateFiltered()) %>% clearMarkerClusters() %>%
addCircleMarkers(~lon, ~lat,
color = "#636363", stroke = TRUE, weight = 1,
fillColor = ~pal(severity), fillOpacity = 0.8,
radius = 5,
popup = ~location,
clusterOptions = markerClusterOptions())
})
}
shinyApp(ui = ui, server = server)